﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Ants.SimpleDistributionProblem.DTO;
using DTO.DTOEventArgs;

namespace Ants.SimpleDistributionProblem
{
    /// <summary>
    /// Class representing simple distribution problem.
    /// </summary>
    public class SimpleDistributionProblem
    {
        private int progress;

        private TimeSpan timeLeft = new TimeSpan();

        /// <summary>
        /// Occurs when progress of finding solution changed.
        /// </summary>
        public event EventHandler<ProgressChangedEventArgs> FindSolutionProgressChanged;

        /// <summary>
        /// Occurs when time left changed.
        /// </summary>
        public event EventHandler<TimeLeftChangedEventArgs> TimeLeftChanged;

        /// <summary>
        /// Gets the progress.
        /// </summary>
        /// <value>
        /// The progress.
        /// </value>
        [Range(0, 100)]
        public int Progress
        {
            get
            {
                return this.progress;
            }

            private set
            {
                int oldValue = this.progress;
                if (value != oldValue)
                {
                    this.progress = value;
                    if (this.FindSolutionProgressChanged != null)
                    {
                        this.FindSolutionProgressChanged.Invoke(
                            this,
                            new ProgressChangedEventArgs()
                            {
                                Progress = value
                            });
                    }
                }
            }
        }

        /// <summary>
        /// Gets the time left.
        /// </summary>
        /// <value>
        /// The time left.
        /// </value>
        public TimeSpan TimeLeft
        {
            get
            {
                return timeLeft;
            }

            private set
            {
                TimeSpan oldValue = this.timeLeft;
                if (oldValue != value)
                {
                    this.timeLeft = value;
                    if (this.TimeLeftChanged != null)
                    {
                        this.TimeLeftChanged.Invoke(
                            this,
                            new TimeLeftChangedEventArgs()
                            {
                                TimeLeft = value
                            });
                    }
                }
            }
        }

        /// <summary>
        /// Finds the solution.
        /// </summary>
        /// <param name="data">The data.</param>
        /// <param name="properties">The properties.</param>
        /// <returns>The solution.</returns>
        public Solution FindSolution(Data data, AntAlgorithmProperties properties)
        {
            this.progress = 0;

            RouteMap routeMap = new RouteMap(data, properties.InitialPheromoneAmount);

            Solution bestSolution = null;
            List<Ant> ants = new List<Ant>();
            int totalAntsToCreate = properties.NumberOfAnts * properties.NumberOfIterations;
            int createdAnts = 0;
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < properties.NumberOfIterations; i++)
            {
                ants.Clear();
                Task[] tasks = new Task[properties.NumberOfThreads];
                Semaphore antsSemaphore = new Semaphore(1, 1);
                Semaphore bestSolutionAndProgressSemaphore = new Semaphore(1, 1);
                for (int j = 0; j < properties.NumberOfThreads; j++)
                {
                    tasks[j] = Task.Factory.StartNew((Action)(() =>
                    {
                        while (true)
                        {
                            antsSemaphore.WaitOne();
                            if (ants.Count < properties.NumberOfAnts)
                            {
                                Ant ant = new Ant();
                                ants.Add(ant);
                                antsSemaphore.Release();
                                ant.FindSolution(routeMap, data.K, properties.Alpha, properties.Beta);
                                bestSolutionAndProgressSemaphore.WaitOne();
                                if (bestSolution == null || ant.Solution.Cost < bestSolution.Cost)
                                {
                                    bestSolution = ant.Solution;
                                }

                                createdAnts++;
                                long averageTicksPerAnt = (sw.Elapsed.Ticks / createdAnts);
                                this.TimeLeft = new TimeSpan((averageTicksPerAnt * totalAntsToCreate) - (createdAnts * averageTicksPerAnt));
                                this.Progress = createdAnts * 100 / totalAntsToCreate;
                                bestSolutionAndProgressSemaphore.Release();
                            }
                            else
                            {
                                antsSemaphore.Release();
                                break;
                            }
                        }
                    }));
                }

                // Block until all tasks complete.
                Task.WaitAll(tasks);
                routeMap.EvaporatePheromone(properties.CoefficientOfEvaporation);
                routeMap.AddPheromone(ants, properties.QParameter);
            }

            sw.Stop();
            this.TimeLeft = new TimeSpan(0);

            return bestSolution;
        }
    }
}